/*
* Copyright (c) 2017 Tatsuya Maki
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.t28.json2java.idea;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.name.Named;
import com.intellij.ide.IdeView;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataKeys;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.application.RunResult;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.InputValidator;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.intellij.util.PlatformIcons;
import io.t28.json2java.idea.command.CommandActionFactory;
import io.t28.json2java.idea.command.NewClassCommandAction;
import io.t28.json2java.idea.exception.ClassAlreadyExistsException;
import io.t28.json2java.idea.exception.InvalidDirectoryException;
import io.t28.json2java.idea.inject.GuiceManager;
import io.t28.json2java.idea.inject.JavaConverterFactory;
import io.t28.json2java.idea.setting.Json2JavaSettings;
import io.t28.json2java.idea.util.Formatter;
import io.t28.json2java.idea.view.NewClassDialog;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Optional;
import java.util.stream.Stream;
public class NewClassAction extends AnAction implements NewClassDialog.ActionListener {
private static final Logger LOGGER = Logger.getInstance(NewClassAction.class);
private static final String NOTIFICATION_DISPLAY_ID = "Json2Java4Idea";
private Project project;
private IdeView ideView;
@Inject
@SuppressWarnings("unused")
private Json2JavaBundle bundle;
@Inject
@SuppressWarnings("unused")
private Json2JavaSettings settings;
@Inject
@Named("Name")
@SuppressWarnings("unused")
private Provider<InputValidator> nameValidatorProvider;
@Inject
@Named("Json")
@SuppressWarnings("unused")
private Provider<InputValidator> jsonValidatorProvider;
@Inject
@SuppressWarnings("unused")
private Provider<CommandActionFactory> commandActionFactoryProvider;
@Inject
@SuppressWarnings("unused")
private Provider<JavaConverterFactory> javaConverterFactoryProvider;
@Inject
@Named("Json")
@SuppressWarnings("unused")
private Provider<Formatter> jsonFormatterProvider;
public NewClassAction() {
super(PlatformIcons.CLASS_ICON);
}
@Override
public void actionPerformed(@Nonnull AnActionEvent event) {
if (!isAvailable(event)) {
return;
}
project = event.getProject();
ideView = event.getData(DataKeys.IDE_VIEW);
final Injector injector = GuiceManager.getInstance(project).getInjector();
injector.injectMembers(this);
// 'selected' is null when directory selection is canceled although multiple directories are chosen.
final PsiDirectory selected = ideView.getOrChooseDirectory();
if (selected == null) {
return;
}
final NewClassDialog dialog = NewClassDialog.builder(project, bundle)
.nameValidator(nameValidatorProvider.get())
.jsonValidator(jsonValidatorProvider.get())
.actionListener(this)
.build();
dialog.show();
}
@Override
public void update(@Nonnull AnActionEvent event) {
final boolean isAvailable = isAvailable(event);
final Presentation presentation = event.getPresentation();
presentation.setEnabledAndVisible(isAvailable);
}
@Override
public void onOk(@Nonnull NewClassDialog dialog) {
final PsiDirectory directory = ideView.getOrChooseDirectory();
if (directory == null) {
dialog.cancel();
return;
}
try {
final CommandActionFactory actionFactory = commandActionFactoryProvider.get();
final JavaConverterFactory converterFactory = javaConverterFactoryProvider.get();
final NewClassCommandAction command = actionFactory.create(
dialog.getClassName(),
dialog.getJson(),
directory,
converterFactory.create(settings)
);
final ProgressManager progressManager = ProgressManager.getInstance();
progressManager.runProcessWithProgressSynchronously(() -> {
final ProgressIndicator indicator = progressManager.getProgressIndicator();
if (indicator != null) {
indicator.setIndeterminate(true);
indicator.setText(bundle.message("progress.text", dialog.getClassName()));
}
final RunResult<PsiFile> result = command.execute();
return result.getResultObject();
}, bundle.message("progress.title"), false, project);
dialog.close();
} catch (RuntimeException e) {
LOGGER.warn("Unable to create a class", e);
onError(dialog, e.getCause());
}
}
@Override
public void onCancel(@Nonnull NewClassDialog dialog) {
dialog.cancel();
}
@Override
public void onFormat(@Nonnull NewClassDialog dialog) {
final Formatter formatter = jsonFormatterProvider.get();
final String formatted = formatter.format(dialog.getJson());
dialog.setJson(formatted);
}
@Override
public void onSettings(@Nonnull NewClassDialog dialog) {
ShowSettingsUtil.getInstance().showSettingsDialog(project, bundle.message("settings.name"));
}
private void onError(@Nonnull NewClassDialog dialog, @Nullable Throwable cause) {
if (cause instanceof ClassAlreadyExistsException) {
// Dialog is not closed or cancelled since user can rename class after message showing
Messages.showMessageDialog(
project,
bundle.message("error.message.class.exists", dialog.getClassName()),
bundle.message("error.title.cannot.create.class"),
Messages.getErrorIcon()
);
return;
}
if (cause instanceof InvalidDirectoryException) {
final Notification notification = new Notification(
NOTIFICATION_DISPLAY_ID,
bundle.message("error.title.directory.invalid"),
bundle.message("error.message.directory.invalid"),
NotificationType.WARNING
);
Notifications.Bus.notify(notification);
dialog.close();
return;
}
final Notification notification = new Notification(
NOTIFICATION_DISPLAY_ID,
bundle.message("error.title.cannot.create.class"),
bundle.message("error.message.cannot.create", dialog.getClassName()),
NotificationType.ERROR
);
Notifications.Bus.notify(notification);
dialog.close();
}
@CheckReturnValue
@VisibleForTesting
@SuppressWarnings("WeakerAccess")
static boolean isAvailable(@Nonnull AnActionEvent event) {
final Project project = event.getProject();
if (project == null) {
return false;
}
final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
if (view == null) {
return false;
}
final ProjectRootManager rootManager = ProjectRootManager.getInstance(project);
final ProjectFileIndex fileIndex = rootManager.getFileIndex();
final Optional<PsiDirectory> sourceDirectory = Stream.of(view.getDirectories())
.filter(directory -> {
final VirtualFile virtualFile = directory.getVirtualFile();
return fileIndex.isUnderSourceRootOfType(virtualFile, JavaModuleSourceRootTypes.SOURCES);
})
.findFirst();
return sourceDirectory.isPresent();
}
}